/**
 * position_internals.js - Javascript chess library
 *
 * http://code.google.com/p/fics-meets-html5/
 *
 * Copyright 2012 Markus Kupke
 */


var chesslib = chesslib || {};

// Position is the constructor for positions
// if called with EPD or FEN it will setup board accordingly
// otherwise it will have initial chess setup
chesslib.Position = function Position (fen){
	"use strict";
   if(fen && typeof fen === 'string'){
	   var rows = fen.split('/');
	   var field = 1, i, j, n;
	   var regexp_castling = /^(K?Q?k?q?|-)$/;
       var rank;
       var filelettersposition;

	   if (rows.length !== 8) throw new chesslib.Position.FENError('FEN not valid');
	   var add_attributes = rows[7].split(/[ \t]+/);
	   if(add_attributes.length<4 || add_attributes.length > 6) throw new chesslib.Position.FENError('FEN: too many attributes for move right, castling, en passant');
	   rows[7]=add_attributes[0];
	   if(add_attributes[1]!=='w' && add_attributes[1]!=='b') throw new chesslib.Position.FENError('FEN not okay; unclear who as right to move');
	   this.white2move=add_attributes[1]==='w';
       if(!regexp_castling.test(add_attributes[2])) throw new chesslib.Position.FENError('FEN: problem with castling string');
	   this.castling = add_attributes[2];
       this.clearboard();
       if(add_attributes[3]!=='-'){
    	   if(add_attributes[3].length!==2) throw new chesslib.Position.FENError("FEN: en passant field not correct");
           filelettersposition=this.constructor.fileletters.indexOf(add_attributes[3][0]);
    	   if(filelettersposition===-1) throw new chesslib.Position.FENError("FEN: No file in en passant field");
    	   filelettersposition++;
           rank=Number(add_attributes[3][1]);
           if (!rank && rank > 8) throw new chesslib.Position.FENError("FEN: en passant field not correct");
           filelettersposition+=(rank-1)*8;         
           this[this.constructor.from_64_to_internal_board_representation(filelettersposition)] = 'e';
       }
	   for(i = 7; i >= 0; i--){
		   for(j = 0; j < rows[i].length; j++){
				if (n = Number(rows[i][j])) {
				   if (n < 1 || n > 8) throw new chesslib.Position.FENError('FEN not valid, numbers must be between 1 and 8');
				   field += Number(rows[i][j]);
			   } else {
				   if (chesslib.Position.allowedletters.indexOf(rows[i][j]) === -1) throw new chesslib.Position.FENError('Malformed FEN string');
				   this[this.constructor.from_64_to_internal_board_representation(field++)] = rows[i][j];
			   }
		   }
	   }
	   if (!this.isvalid()) throw new chesslib.Position.FENError('Position contains inconsistencies');  
   }
};

//constants, offset and internal board representation from
//"Schach am PC" by Dieter Steinwender and Frederic A. Friedel
chesslib.Position.offset_knight = [ 19 , 21 , 12 , -8 , -19 , -21 , -12 , 8 ];
chesslib.Position.offset_other = [{off:[ -9 , -11 , 9, 11 ],
		pieces:['QBK', 'qbk']
		},
		{off:[ -1 , 10 , 1 , -10 ],
		pieces:['QRK', 'qrk']
		}];
chesslib.Position.fileletters="abcdefgh";
chesslib.Position.allowedletters = 'kKqQpPrRnNbB';
chesslib.Position.FENError = function FENError (){};
chesslib.Position.FENError.prototype = Object.create(Error.prototype);
chesslib.Position.FENError.prototype.constructor = chesslib.Position.FENError;
chesslib.Position.Move = function Move (){};
chesslib.Position.Move.prototype = Object.create(Object.prototype);
chesslib.Position.Move.prototype.constructor = chesslib.Position.Move;
chesslib.Position.Move.prototype.toString = function(){
	var s = this.figure + chesslib.Position.internalboard2humanboard(this.from) +
	(this.capture?"x":"-") + chesslib.Position.internalboard2humanboard(this.to);
	if(this.hasOwnProperty('changetofigure')) s += this.changetofigure;
	return s;
};
chesslib.Position.from_64_to_internal_board_representation = function( i ) {
	"use strict";
	if( i<1 || i>64 ) { 
		throw {
			name	: "from_64_to_internal_board_representation: boudary violation",
			message	: "from_64_to_internal_board_representation: A chess board does have 64 fields :-)"
			};
		}
	return i + Math.floor((i-1)/8) * 2 + 20;
};

//the board is part of the prototype: every position will inherit it
//a move on the board will be represented by prototypal inheritance
chesslib.Position.prototype[21]	= 'R';
chesslib.Position.prototype[22]	= 'N';
chesslib.Position.prototype[23]	= 'B';
chesslib.Position.prototype[24]	= 'Q';
chesslib.Position.prototype[25]	= 'K';
chesslib.Position.prototype[26]	= 'B';
chesslib.Position.prototype[27]	= 'N';
chesslib.Position.prototype[28]	= 'R';
chesslib.Position.prototype[31]	= 'P';
chesslib.Position.prototype[32]	= 'P';
chesslib.Position.prototype[33]	= 'P';
chesslib.Position.prototype[34]	= 'P';
chesslib.Position.prototype[35]	= 'P';
chesslib.Position.prototype[36]	= 'P';
chesslib.Position.prototype[37]	= 'P';
chesslib.Position.prototype[38]	= 'P';
chesslib.Position.prototype[41]	= '0';
chesslib.Position.prototype[42]	= '0';
chesslib.Position.prototype[43]	= '0';
chesslib.Position.prototype[44]	= '0';
chesslib.Position.prototype[45]	= '0';
chesslib.Position.prototype[46]	= '0';
chesslib.Position.prototype[47]	= '0';
chesslib.Position.prototype[48]	= '0';
chesslib.Position.prototype[51]	= '0';
chesslib.Position.prototype[52]	= '0';
chesslib.Position.prototype[53]	= '0';
chesslib.Position.prototype[54]	= '0';
chesslib.Position.prototype[55]	= '0';
chesslib.Position.prototype[56]	= '0';
chesslib.Position.prototype[57]	= '0';
chesslib.Position.prototype[58]	= '0';
chesslib.Position.prototype[61]	= '0';
chesslib.Position.prototype[62]	= '0';
chesslib.Position.prototype[63]	= '0';
chesslib.Position.prototype[64]	= '0';
chesslib.Position.prototype[65]	= '0';
chesslib.Position.prototype[66]	= '0';
chesslib.Position.prototype[67]	= '0';
chesslib.Position.prototype[68]	= '0';
chesslib.Position.prototype[71]	= '0';
chesslib.Position.prototype[72]	= '0';
chesslib.Position.prototype[73]	= '0';
chesslib.Position.prototype[74]	= '0';
chesslib.Position.prototype[75]	= '0';
chesslib.Position.prototype[76]	= '0';
chesslib.Position.prototype[77]	= '0';
chesslib.Position.prototype[78]	= '0';
chesslib.Position.prototype[81]	= 'p';
chesslib.Position.prototype[82]	= 'p';
chesslib.Position.prototype[83]	= 'p';
chesslib.Position.prototype[84]	= 'p';
chesslib.Position.prototype[85]	= 'p';
chesslib.Position.prototype[86]	= 'p';
chesslib.Position.prototype[87]	= 'p';
chesslib.Position.prototype[88]	= 'p';
chesslib.Position.prototype[91]	= 'r';
chesslib.Position.prototype[92]	= 'n';
chesslib.Position.prototype[93]	= 'b';
chesslib.Position.prototype[94]	= 'q';
chesslib.Position.prototype[95]	= 'k';
chesslib.Position.prototype[96]	= 'b';
chesslib.Position.prototype[97]	= 'n';
chesslib.Position.prototype[98]	= 'r';
chesslib.Position.prototype.length = 99;
chesslib.Position.prototype.white2move = true;
chesslib.Position.prototype.castling = 'KQkq';

chesslib.Position.prototype.legalmoves = function(){
"use strict";
	var moves = [], pseudo_moves = [], m, p_after_move;
	var dir = this.white2move?-1:1;
	var p;
	if(!(this.hasOwnProperty("legals"))){
		pseudo_moves = this.pseudomoves();
		for (m in pseudo_moves) {
			if (!(pseudo_moves.hasOwnProperty(m))) continue;
			p = pseudo_moves[m];
			if (p.castling) {moves.push(p);continue;} //castling moves are already checked for validity
			p_after_move = Object.create(this); // now we use prototypical inheritance to produce a position after a move
			p_after_move.white2move = !this.white2move;
			p_after_move[p.from] = '0';
			p_after_move[p.to] = p.figure;
			if(this[p.to] === 'e'){//we have an en passant capture
				p_after_move[(p.to)+10*dir] = '0';
			}
			if(p.changetofigure) p_after_move[p.to] = p.changetofigure;
			if(p_after_move.isvalid())
			    moves.push(p);
		};
		this.legals = moves;
	}
	return this.legals;
};


chesslib.Position.prototype.pseudomoves = function(){
	//generate all pseudomoves regardless check
	var moves = [];
	var pieces_pos = []; //holds the positions of the figure we are interested in
	var move;
	var i, j, o, p, p2, figures, piece_pos, figset;
	var friends = this.white2move ? 'QBNRPK' : 'qbnrpk';
	var second_row, eight_row;
	var offset_knight = this.constructor.offset_knight;
	var offset, dir;
	var offset_other = this.constructor.offset_other;
	var Move = this.constructor.Move;
	var changefigures = this.white2move ? 'QRBN':'qrbn';
	var king = this.white2move ? 'K':'k';
	var castling_kingside_letter, castling_queenside_letter;
	var castling_kingside_fields, castling_queenside_fields;

	if(!(this.hasOwnProperty('pseudos'))){

		//start with the knights
		var knight = this.white2move ? 'N' : 'n';
		Array.prototype.forEach.call(this, function(fig, i){
			if (fig === knight) pieces_pos.push(i);
		});
		for (i in pieces_pos) {
			if(!pieces_pos.hasOwnProperty(i)) continue;
			piece_pos = pieces_pos[i];
			for (o in offset_knight) {
				if(!offset_knight.hasOwnProperty(o)) continue;
				p = piece_pos + offset_knight[o];
				if(!this[p]) continue;
				if(!(friends.indexOf(this[p]) === -1)) continue; //not allowed to capture own pieces
				move = new Move;
				move.figure = knight;
				move.from = piece_pos;
				move.to = p;
				if(!(this[p] === '0' || this[p] === 'e')) {
					move.capture = true;
				}
				moves.push(move);
			};
		};
		//next the other pieces others than pawn
		//diagonal first then the files
		for(figset = 0; figset <=1 ; figset ++){
			offset = offset_other[figset].off;
			figures = this.white2move ? offset_other[figset].pieces[0]:offset_other[figset].pieces[1];
			pieces_pos.length = 0;
			Array.prototype.forEach.call(this, function(fig, i){
				if (!(figures.indexOf(fig) === -1)) pieces_pos.push(i);
			});
			for (i in pieces_pos) {
				if(!pieces_pos.hasOwnProperty(i)) continue;
				piece_pos = pieces_pos[i];
				for(o in offset){
					if(!offset.hasOwnProperty(o)) continue;
					for (j=1;j<8;j++){
						if(this[piece_pos]===king && j ===2) break;
						p = piece_pos + offset[o] * j;
						if(!this[p])break;
						if(!(friends.indexOf(this[p]) === -1)) break; //not allowed to capture own pieces
						move = new Move;
						move.figure = this[piece_pos];
						move.from = piece_pos;
						move.to = p;
						if(!(this[p] === '0' || this[p] === 'e')) {
							move.capture = true;
							moves.push(move);
							break;
						}
						moves.push(move);
					}
				};
			};	
		}
		//now the pawns
		figures = this.white2move ? 'P':'p';
		dir = this.white2move ? 1 : -1;
		second_row = this.white2move ? 30 : 80;
		eight_row = this.white2move ? 90 : 20;
		pieces_pos.length = 0;
		Array.prototype.forEach.call(this, function(fig, i){
			if (fig === figures) pieces_pos.push(i);
		});
		for (i in pieces_pos){
			if(!(pieces_pos.hasOwnProperty(i))) continue;
			piece_pos = pieces_pos[i];
			for (offset = 9; offset <= 11; offset=offset + 2) {   //capture moves
				p = piece_pos + dir * offset; 
				if(!this[p]) continue;
				if (this[p]==='0'||(!(friends.indexOf(this[p])===-1))) continue;
				move = new Move;
				move.figure = this[piece_pos];
				move.from = piece_pos;
				move.to = p;
				move.capture = true;
				if(this[p]==='e') {
					move.pawnToRemove = p - dir * 10;
				};
				moves.push(move);
			};
			p = piece_pos + dir * 10;
			if (!(this[p]==='0')) continue;
			if (0 < (p - eight_row) && (p - eight_row) < 9) { //if the pawn steps to the eighth row it becomes a different figure
				for (j = 0; j < changefigures.length; j++){
					move = new Move;
					move.figure = figures;
					move.from = piece_pos;
					move.to = p;
					move.changetofigure = changefigures[j];
					moves.push(move);
				}
			} else {
				move = new Move;
				move.figure = figures;
				move.from = piece_pos;
				move.to = p;
				moves.push(move);
				if (0 < (piece_pos - second_row) && (piece_pos - second_row) < 9) { // a pawn on the second row may step two fields at once
					p2 = p + dir * 10;
					if (this[p2]==='0')
					{
						move = new Move;
						move.figure = figures;
						move.from = piece_pos;
						move.to = p2;
						move.enpassant = p;
						moves.push(move);
					};
				};
			};
		}
		//castling; we don't regard castling as possible pseudo move if king or field between
		//king and rook is threatened

		castling_kingside_letter = this.white2move?'K':'k';
		castling_queenside_letter = this.white2move?'Q':'q';
		castling_kingside_fields = this.white2move?[26,27]:[96,97];
		castling_queenside_fields = this.white2move?[22,23,24]:[92,93,94];
		kings_position = this.white2move?25:95;
		var king_in_check;
		if(!(this.castling.indexOf(castling_kingside_letter)===-1)) {
			if(this[castling_kingside_fields[0]]==='0'&&this[castling_kingside_fields[1]]==='0'){
				if(king_in_check = this.threats(kings_position,!this.white2move)){
					if(this.threats(castling_kingside_fields[0],!this.white2move)&&this.threats(castling_kingside_fields[1],!this.white2move)){
						move = new Move;
						move.figure = king;
						move.from = kings_position;
						move.to = castling_kingside_fields[1];
						move.castling = true;
						moves.push(move);
					};
				};
			};
		}
		if(!(this.castling.indexOf(castling_queenside_letter)===-1)) {
			if(this[castling_queenside_fields[0]]==='0'&&this[castling_queenside_fields[1]]==='0'&&this[castling_queenside_fields[2]]==='0'){
				if (king_in_check === undefined) king_in_check = this.threats(kings_position,!this.white2move);
				if (king_in_check) {
					if(this.threats(castling_queenside_fields[1],!this.white2move)&&this.threats(castling_queenside_fields[2],!this.white2move)){
						move = new Move;
						move.figure = king;
						move.from = kings_position;
						move.to = castling_queenside_fields[1];
						move.castling = true;
						moves.push(move);
					}
				}
			}
		};
		this.pseudos = moves;
	};
	return this.pseudos;
};

chesslib.Position.prototype.move = function(m){
	var newPos = Object.create(this);
	if (!(m instanceof chesslib.Position.Move)) throw new Error("This is not a move");
	newPos.white2move = !newPos.white2move;
	// first delete old en passant information
	var idx = Array.prototype.indexOf.call(newPos, 'e');
	if (!(idx === -1)) newPos[idx] = '0';
	newPos[m['from']] = '0';
	newPos[m['to']] = m['figure'];
	if (m['changetofigure']) newPos[m['to']] = m['changetofigure'];
	if (m['enpassant']) newPos[m['enpassant']] = 'e';
	if (m["pawnToRemove"]) newPos[m["pawnToRemove"]] = '0'; 
	if (m.castling) {
		if ((m['from'] - m['to'])>0) { //queen side castling
			newPos[m['from'] - 1] = newPos[m['from'] - 4];
			newPos[m['from'] - 4] = '0';
		} else { //king side castling
			newPos[m['from'] + 1] = newPos[m['from'] + 3];
			newPos[m['from'] + 3] = '0';
		};
	};
	return newPos;
};

chesslib.Position.prototype.threats = function(field, white, all){
	// examines the threats on a given field; white gives the side that influences
	// the field; if true all white threats on a field are examined
	// in order to speed up the routine to check whether the king is in check we
	// use all: if true the routine gives back all threats in an array 
	// with values of internal representation. If false the routine
	// stops after the first threat and gives back false (or true if no threat).
	"use strict";
	var threats = [];
	var i, p, loopi, checkpieces, dir, pawn;
	var friends = white ? 'qbnrpk' : 'QBNRPK';
	// performance
	var offset_knight = this.constructor.offset_knight;
	var offset_other = this.constructor.offset_other;

	if (!this[field])
		throw new Error("function threats must have a valid field to check");
	if (white !== true && white !== false)
		throw new Error(
				"functiion threats must now the color that imposes threats");
	// check whether a knight threatens the field
	var knight2check = white ? 'N' : 'n';
	for (i in offset_knight) {
		if (!offset_knight.hasOwnProperty(i))
			continue;
		p = field + offset_knight[i];
		if (!this[p])
			continue;
		if (this[p] === knight2check)
			if (all)
				threats.push(p);
			else
				return false;
	}
	;

	// check whether queen, rook or bishop threatens the field
	for (i in offset_other) {
		if (!(offset_other.hasOwnProperty(i)))
			continue;
		checkpieces = white ? offset_other[i]["pieces"][0]
				: offset_other[i]["pieces"][1];
		for (dir in offset_other[i]["off"]) {
			if (!offset_other[i]["off"].hasOwnProperty(dir))
				continue;
			for (loopi = 1; loopi < 9; loopi++) {
				p = field + loopi * offset_other[i]["off"][dir];
				if (!this[p])
					break; // we are off the board
				if (this[p] === '0' || this[p] === 'e')
					continue;
				if (!(friends.indexOf(this[p]) === -1))
					break; // in this direction is a friend
				if (checkpieces.indexOf(this[p]) === -1) {
					break; // bless the lord; it is a rook or a pawn
				} else if (checkpieces.indexOf(this[p]) === 2 && loopi > 1) {
					break; // the king is too far away
				} else {
					if (all) {
						threats.push(p);
						break
					} else
						return false;
				}
			}
		}
	}
	// now take care of the pawns
	dir = white ? -1 : 1;
	pawn = white ? 'P' : 'p';
	p = this[field + (dir * 11)];
	if (p) {
		if (p === pawn) {
			if (all) {
				threats.push(p)
			} else
				return false;
		}
	}
	p = this[field + (dir * 9)];
	if (p) {
		if (p === pawn)
			if (all) {
				threats.push(p)
			} else
				return false;
	}
	return true;
};

chesslib.Position.prototype.isvalid = function(){
	//checks whether the position is valid;
	"use strict";
	// the side that moved must not be in check
	var king2check = this.white2move?'k':'K';
	var king_position = Array.prototype.indexOf.call(this, king2check);
    return this.threats(king_position, this.white2move, false);
};
chesslib.Position.prototype.clearboard = function(){
	"use strict";
	var i;
	for(i=1;i<=64;i=i+1){
		this[this.constructor.from_64_to_internal_board_representation(i)] = '0';
	};
};


chesslib.Position.internalboard2humanboard = function (pos){
	"use strict";
	var letters="abcdefgh";
	return letters[(pos%10)-1] + (Math.floor(pos/10) - 1).toString();
};

// toString of a position prints out FEN of the position
chesslib.Position.prototype.toString = function(){
	var i, j, string, empty_counter, en_passant, sym;
	en_passant = '-';
	string = "";
	empty_counter = 0;
	for (i=7;i>=0;i--){
		for (j=1;j<9;j++){
		  sym = this[this.constructor.from_64_to_internal_board_representation(i*8+j)]; 
		  if(sym ==='0'){
			empty_counter++;
			if(j<8)continue;
		  }
		  if(sym ==="e"){ //en_passant
			  empty_counter++;
			  en_passant = this.constructor.fileletters[j-1]+(i+1).toString();
			  if(j<8)continue;
		  }
		  if(empty_counter) string+=empty_counter;
		  empty_counter=0;
		  if(sym!=='0' && sym!=='e') string+=sym;
		}
		if(i>0)string+="/";
	}
	string+=this.white2move?' w':' b';
	string+=this.castling?' '+this.castling:' -';
	string+=' ' + en_passant;
	return string;
};

chesslib.Position.prototype.toJSON = function (){
	o = {};
    for (var i = 21; i <= 98; i++) {
    	if(!(this[i])) continue;
    	var field = chesslib.Position.internalboard2humanboard(i);
    	o[field] = this[i];
    };
	return o;
};
